#ifndef CLICK_MANAGER_H
#define CLICK_MANAGER_H

#define MOUSE_LEFT    0x01
#define MOUSE_RIGHT   0x02
#define MOUSE_MIDDLE  0x04

#define MOUSE_BUTTONS (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)

#define MOUSE_SHIFT    0x10
#define MOUSE_CTRL     0x20
#define MOUSE_ALT      0x40

#define MOUSE_FXN_KEYS (MOUSE_SHIFT | MOUSE_CTRL | MOUSE_ALT)

// Set when a button is pressed and a button was already pressed,
// or when a button is pressed and the mouse is moved.
#define MOUSE_DRAGGING            0x100
// Only sent once, until all buttons are released.
#define MOUSE_DRAGGING_STARTED    0x200
// Leaving window
#define MOUSE_LEAVING             0x400

// Milliseconds between two downs.
#define DOUBLE_CLICK_TIME 400

int CursorMoved(int startx, int starty, int x, int y);

template<class T>
class ClickManager : public T {
private:
	// All for mouse selection.
	unsigned int lastClickTime;
	unsigned int lastClickButton;
	int clickCount;
	int hasCapture;
	unsigned int mouseState;
	int startx, starty;
	int lastx, lasty;

	// Used for merging multiple wheel clicks into single undo item.
	// Not used by all child classes.
	int lastActionWasWheel;
protected:

	ClickManager(wxWindow *parent, unsigned int style) {
		ResetMouseState();
	}

	// Resets all state, include double click info.  Only call on
	// create or gain/lose focus.
	void ResetMouseState() {
		lastClickButton = 0;
		mouseState = 0;
		hasCapture = 0;
		clickCount = 0;
		lastx = lasty = startx = starty = 0;
		lastActionWasWheel = 0;
	}

	void ResetClickCount() {
		clickCount = 0;
	}

	void ResetStartPos() {
		startx = lastx;
		starty = lasty;
	}

	// Only makes sense while or just after dragging.
	void GetDragRect(int *xStart, int *yStart, int *xStop, int *yStop) {
		*xStart = startx;
		*yStart = starty;
		*xStop = lastx;
		*yStop = lasty;
	}

	virtual void MouseDown(unsigned int button, int clickCount, unsigned int flags, int x, int y) {}

	virtual void MouseMove(unsigned int flags, int x, int y) {}
	virtual void MouseDrag(unsigned int flags, int xStart, int yStart, int xStop, int yStop) {}
	// Also called on fxn key up.
	virtual void MouseUp(unsigned int button, int clickCount, unsigned int flags, int x, int y) {}
	virtual void MouseWheel(unsigned int wheel, int delta, unsigned int flags, int lastActionWasWheel) {}
	virtual void CaptureLost() {}

	void SetStartPos(int x, int y) {
		startx = x;
		starty = y;
	}

private:
	void UpdateState(wxMouseEvent& evt) {
		lastActionWasWheel = 0;
		mouseState &= ~(MOUSE_BUTTONS | MOUSE_FXN_KEYS);
		if (evt.LeftIsDown()) mouseState |= MOUSE_LEFT;
		if (evt.RightIsDown()) mouseState |= MOUSE_RIGHT;
		if (evt.MiddleIsDown()) mouseState |= MOUSE_MIDDLE;
		if (evt.ShiftDown()) mouseState |= MOUSE_SHIFT;
		if (evt.ControlDown()) mouseState |= MOUSE_CTRL;
		if (evt.CmdDown()) mouseState |= MOUSE_ALT;
		evt.GetPosition(&lastx, &lasty);
	}

	void OnMouseDown(wxMouseEvent& evt) {
		// Needed on some platforms for proper activation
		// behavior, apparently.
		evt.Skip();
		if (!hasCapture) {
			mouseState = 0;
			hasCapture = 1;
			T::CaptureMouse();
		}
		UpdateState(evt);
		int buttonsDown = 0;
		if (mouseState & MOUSE_LEFT) buttonsDown ++;
		if (mouseState & MOUSE_MIDDLE) buttonsDown ++;
		if (mouseState & MOUSE_RIGHT) buttonsDown ++;

		unsigned int t = lastClickTime;
		lastClickTime = wxGetLocalTimeMillis().GetLo();

		clickCount ++;
		if ((mouseState & (MOUSE_BUTTONS | MOUSE_FXN_KEYS)) != lastClickButton ||
			CursorMoved(startx, starty, lastx, lasty) ||
			lastClickTime - t > DOUBLE_CLICK_TIME) {
					clickCount = 1;
		}
		lastClickButton = mouseState & (MOUSE_BUTTONS | MOUSE_FXN_KEYS);
		if (buttonsDown > 1 || (mouseState & MOUSE_DRAGGING)) {
			mouseState |= MOUSE_DRAGGING;
			clickCount = 0;
		}

		ResetStartPos();

		int button = 0;
		if (evt.LeftDown()) button = MOUSE_LEFT;
		else if (evt.RightDown()) button = MOUSE_RIGHT;
		else if (evt.MiddleDown()) button = MOUSE_MIDDLE;
		MouseDown(button, clickCount, mouseState, lastx, lasty);
	}

	void OnMouseMove(wxMouseEvent& evt) {
		UpdateState(evt);
		unsigned int bonusFlags = 0;
		if (evt.Leaving()) {
			bonusFlags |= MOUSE_LEAVING;
		}
		if (mouseState & MOUSE_BUTTONS) {
			if ((mouseState & MOUSE_DRAGGING) || CursorMoved(startx, starty, lastx, lasty)) {
				clickCount = 0;
				if (!(mouseState & MOUSE_DRAGGING)) {
					mouseState |= MOUSE_DRAGGING;
					bonusFlags |= MOUSE_DRAGGING_STARTED;
				}
				MouseDrag(mouseState | bonusFlags, startx, starty, lastx, lasty);
				return;
			}
		}
		if (!(mouseState & MOUSE_BUTTONS)) {
			MouseMove(mouseState | bonusFlags, lastx, lasty);
			return;
		}
	}

	void OnMouseUp(wxMouseEvent& evt) {
		int button = 0;
		if (evt.LeftUp()) button = MOUSE_LEFT;
		else if (evt.RightUp()) button = MOUSE_RIGHT;
		else if (evt.MiddleUp()) button = MOUSE_MIDDLE;
		UpdateState(evt);
		if (hasCapture && !(mouseState & MOUSE_BUTTONS)) {
			hasCapture = 0;
			T::ReleaseMouse();
		}
		MouseUp(button, clickCount, mouseState, lastx, lasty);
	}

	void OnMouseDclick(wxMouseEvent& evt) {}

	void OnCaptureLost(wxMouseCaptureLostEvent& evt) {
		ResetMouseState();
		CaptureLost();
	}

	void OnMouseWheel(wxMouseEvent& evt) {
		UpdateState(evt);
		lastActionWasWheel = 1;
		int rot = evt.GetWheelRotation();
		// ???
		if (!rot) return;
		int delta = evt.GetWheelDelta();
		if (delta) rot /= delta;
#if wxCHECK_VERSION(2, 9, 0)
		MouseWheel(evt.GetWheelAxis(), rot, mouseState, lastActionWasWheel);
#else
		MouseWheel(0, rot, mouseState, lastActionWasWheel);
#endif
	}

	void OnKeyUp(wxKeyEvent& evt) {
		int code = evt.GetKeyCode();
		if (code == WXK_SHIFT && (mouseState & MOUSE_BUTTONS)) {
			mouseState &= ~MOUSE_SHIFT;
			MouseUp(MOUSE_SHIFT, mouseState, clickCount, lastx, lasty);
		}
	}

	/*
	void OnKeyDown(wxKeyEvent& evt) {
		int code = evt.GetKeyCode();
		if (code == WXK_SHIFT) {
		}
	}//*/

	DECLARE_EVENT_TABLE()
};


BEGIN_EVENT_TABLE_TEMPLATE1(ClickManager, T1, T1)
	EVT_LEFT_DOWN(ClickManager<T1>::OnMouseDown)
	EVT_RIGHT_DOWN(ClickManager<T1>::OnMouseDown)

	EVT_LEFT_UP(ClickManager<T1>::OnMouseUp)
	EVT_RIGHT_UP(ClickManager<T1>::OnMouseUp)

	EVT_MIDDLE_UP(ClickManager<T1>::OnMouseUp)
	EVT_MIDDLE_DOWN(ClickManager<T1>::OnMouseDown)

	EVT_RIGHT_DCLICK(ClickManager<T1>::OnMouseDown)
	EVT_LEFT_DCLICK(ClickManager<T1>::OnMouseDown)
	EVT_MOTION(ClickManager<T1>::OnMouseMove)
	EVT_MOUSE_CAPTURE_LOST(ClickManager<T1>::OnCaptureLost)

	EVT_MOUSEWHEEL(ClickManager<T1>::OnMouseWheel)

	EVT_KEY_UP(ClickManager<T1>::OnKeyUp)

	EVT_LEAVE_WINDOW(ClickManager<T1>::OnMouseMove)
END_EVENT_TABLE()

extern int wxGLCanvasInitArgs[];

template<>
inline ClickManager<wxGLCanvas>::ClickManager(wxWindow *parent, unsigned int style) : wxGLCanvas(parent, wxID_ANY, wxGLCanvasInitArgs, wxDefaultPosition, wxDefaultSize, style) {
	ResetMouseState();
}

template<>
inline ClickManager<wxPanel>::ClickManager(wxWindow *parent, unsigned int style) : 
wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style) {
	ResetMouseState();
}

#endif
